package dryven.util.json.dryvenjson.encoders;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

import dryven.model.di.LocalThreadStorage;
import dryven.reflection.DeclaringMethodIterator;
import dryven.util.json.JsonEncoder;
import dryven.util.json.dryvenjson.FieldPolicy;
import dryven.util.json.dryvenjson.JSONCompositeEncoder;
import dryven.util.json.dryvenjson.JSONTypeEncoder;

public class ObjectEncoder implements JSONTypeEncoder {
	
	@Override
	public void encodeValue(Object value, JSONCompositeEncoder e, FieldPolicy p, LocalThreadStorage diStorage, Appendable dest) throws IOException {
		boolean isFirstField = true;
		dest.append('{');
		Class<?> type = value.getClass();
		//list all public fields
		isFirstField = addFields(value, e, dest, isFirstField, type, p, diStorage);
		//list all getters
		isFirstField = addMethods(value, e, dest, isFirstField, type, p, diStorage);
		dest.append('}');
	}

	private boolean addMethods(Object value, JSONCompositeEncoder e,
			Appendable dest, boolean isFirstField, Class<?> type, FieldPolicy p, LocalThreadStorage diStorage) throws IOException {
		for (Method m:new DeclaringMethodIterator(type)) {
			if(p.includeMethod(m)) {
				if(!isFirstField) {
					dest.append(',');
				}
				encodeField(p.getFieldName(m), p.getValueFromMethod(m, value, diStorage), m.getReturnType(), e, diStorage,dest);
				isFirstField = false;
			}
		}
		return isFirstField;
	}

	private boolean addFields(Object value, JSONCompositeEncoder e,
			Appendable dest, boolean isFirstField, Class<?> type, FieldPolicy p, LocalThreadStorage diStorage) throws IOException {
		for (Field f: type.getFields()) {
			if(p.includeField(f)) {
				if(!isFirstField) {
					dest.append(',');
				}
				encodeField(p.getFieldName(f), p.getValueFromField(f, value, diStorage), f.getType(), e, diStorage, dest);
				isFirstField = false;
			}
		}
		return isFirstField;
	}
	
	private void encodeField(String name, Object value, Class<?> type, JSONCompositeEncoder e, LocalThreadStorage diStorage, Appendable dest) throws IOException {
		e.encodePropertyValue(name, diStorage, dest);
		dest.append(':');
		
		if(value==null) {
			e.encodeNull(type, dest,true);
		} else {
			e.encodePropertyValue(value,diStorage,dest);
		}
	}

	@Override
	public boolean isApplicableForType(Class<?> type) {
		return true;
	}

}
